﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using OptusMobile.Web.ServerComponents.Common;
using OptusMobile.Web.ServerComponents.DAL;
using RecommenderAlgorithmLibrary;
using OptusMobile.Web.ServerComponents.BAL.CustomerManagement;
using OptusMobile.Web.ServerComponents.BAL.HandsetManagement;
using OptusMobile.Web.ServerComponents.BAL.PlanManagement;
using System.Collections;

namespace OptusMobile.Web.ServerComponents.BAL.RecommenderManagement
{
    public class RecommenderManager :EFManagerBase
    {
        IBCFandUBCFHybridAlgorithm algorithmLibrary = new IBCFandUBCFHybridAlgorithm();
        CustomerManager customerManager = new CustomerManager();
        HandsetManager handsetManager = new HandsetManager();
        PlanManager planManager = new PlanManager();
        // the number of top neighbors used in CF algorithm
        private int TOPN = 10;
        int thresholdUserRatingNumber = 1;
        double thresholdHandsetSimilarity = 0.9;

        public List<Rating> ReadAllRatings()
        {
            try
            {
                return this.ObjectContext.RatingSet.Include("HandsetBasicInformation").Include("Customer").ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Recommend handsets using all user ratings 
        /// </summary>
        /// <param name="CustomerID">The customer ID</param>
        /// <returns>The handsets for recommendation</returns>
        public List<V_HandsetBasicInformation> RecommendHandsetsUsingAllRatings(string customerID, string mobileNumber)
        {
            List<V_HandsetBasicInformation> recommenders = new List<V_HandsetBasicInformation>();

            List<Customer> allCustomers = customerManager.GetAllCustomers();
            List<V_HandsetBasicInformation> allHandsets = handsetManager.GetAllHandsetBasicInformation();
            List<Rating> allRatings = ReadAllRatings();

            List<Rating> userRatedRatings = allRatings.Where(r => r.Customer.customerID == customerID).ToList();

            if (userRatedRatings.Count > thresholdUserRatingNumber)
            {
                recommenders = RecommendHandsets(customerID, allRatings, allCustomers, allHandsets, 10);
            }
            else
            {
                CustomerContract customerContract = customerManager.GetCustomerContractInformation(mobileNumber);
                string handsetID =  customerContract.HandsetContract.Cap.HandsetBasicInformation.handsetID;

                var handsets = handsetManager.GetHandsetSimilarityByAttribute().Where(
                    p => p.handsetID == handsetID && p.similarty > thresholdHandsetSimilarity);
                foreach (V_HandsetSimilarity handset in handsets)
                {
                    V_HandsetBasicInformation mobilPhone = handsetManager.GetViewHandsetBasicInformation(handset.campareHandsetID);
                    if (mobilPhone != null)
                    {
                        recommenders.Add(mobilPhone);
                    }
                }
            }

            return recommenders;
        }

        public List<V_HandsetBasicInformation> RecommendHandsets(string customerID, List<Rating> ratings, List<Customer> users, List<V_HandsetBasicInformation> handsets, int recommenderNumber)
        {
            List<V_HandsetBasicInformation> recommenders = new List<V_HandsetBasicInformation>();

            List<string> userIDs = (from customer in users
                                    select customer.customerID).ToList();

            List<string> itemIDs = (from handset in handsets
                                    select handset.handsetID).ToList();

            int?[][] originalRatings = new int?[users.Count][];
            for (int i = 0; i < originalRatings.Length; i++)
            {
                int?[] row = new int?[handsets.Count];
                originalRatings[i] = row;
            }

            foreach (Rating rating in ratings)
            {
                int userIndex = userIDs.IndexOf(rating.Customer.customerID);
                int itemIndex = itemIDs.IndexOf(rating.HandsetBasicInformation.handsetID);
                originalRatings[userIndex][itemIndex] = Convert.ToInt32(rating.ratingMark);
            }

            int targetUserIndex = userIDs.IndexOf(customerID);

            List<int> recommendedItemsIndexes = algorithmLibrary.RecommendItems(originalRatings, targetUserIndex, recommenderNumber,TOPN);
            foreach (int index in recommendedItemsIndexes)
            {
                recommenders.Add(handsets[index]);
            }

            return recommenders;
        }

        /// <summary>
        /// Recommends new contract to customer, including plan and extra package.
        /// </summary>
        /// <param name="historyRecords">The usage history records of the user.</param>
        /// <param name="contract">The current contract of the user.</param>
        /// <returns>The new contract for recommendation</returns>
        public HandsetContract RecommendPlan(List<CustomerUsageHistroy> historyRecords, HandsetContract contract)
        {
            HandsetContract newContract = new HandsetContract();
            newContract.Cap = new Cap();

            //selects the latest 6 months data for analysis
            List<CustomerUsageHistroy> usedHistoryRecords = historyRecords.OrderByDescending(o => o.buildDate).Take(6).ToList();

            CustomerUsageHistroy historyAverage = new CustomerUsageHistroy();
            historyAverage.callsToOptus = (int)usedHistoryRecords.Average(h => h.callsToOptus);
            historyAverage.callsToOther = (int)usedHistoryRecords.Average(h => h.callsToOther);
            historyAverage.data = usedHistoryRecords.Average(h => h.data);
            historyAverage.messageToOptus = (int)usedHistoryRecords.Average(h => h.messageToOptus);
            historyAverage.messageToOther = (int)usedHistoryRecords.Average(h => h.messageToOther);
            historyAverage.totalCallTimeToOptus = (int)usedHistoryRecords.Average(h => h.totalCallTimeToOptus);
            historyAverage.totalCallTimeToOther = (int)usedHistoryRecords.Average(h => h.totalCallTimeToOther);

            OptusMobile.Web.ServerComponents.DAL.Plan currentPlan = contract.Cap.Plans;
            ExtraData dataPackage = contract.ExtraData;
            ExtraMessage messagePackage = contract.ExtraMessage;

            PlanManager planManager = new PlanManager();
            List<ServerComponents.DAL.Plan> allPlans = planManager.ReadAllPlans();
            List<ExtraData> allDataPackages = planManager.ReadAllDataPackages();
            List<ExtraMessage> allMessagePackages = planManager.ReadAllMessagePackages();

            //calculates the actual fee of every plan and package combo
            List<ArrayList> compareResults = new List<ArrayList>();
            foreach (ServerComponents.DAL.Plan plan in allPlans)
            {
                if (plan.monthlyFee >= currentPlan.monthlyFee)
                {
                    foreach(ExtraMessage package in allMessagePackages)
                    {
                        if (package.exMessagePrice >= messagePackage.exMessagePrice)
                        {
                            ArrayList list = new ArrayList();
                            decimal actualFee = planManager.CalculateActualValue(plan, historyAverage, package);
                            list.Add(actualFee);
                            list.Add(plan);
                            list.Add(package);

                            compareResults.Add(list);
                        }
                    }
                }
            }

            //selects the cheapest combo
            ArrayList bestCombo = compareResults[0];
            foreach (ArrayList list in compareResults)
            {
                if (Convert.ToDecimal(list[0]) < Convert.ToDecimal(bestCombo[0]))
                {
                    bestCombo = list;
                }
            }
            newContract.Cap.Plans = bestCombo[1] as ServerComponents.DAL.Plan;
            newContract.ExtraMessage = bestCombo[2] as ExtraMessage;

            //Finds the best data package
            // the amount of data worth 5 dollars
            decimal gapData = 5/(decimal)0.015/(decimal)1024;
            if ((decimal)historyAverage.data < dataPackage.exDataAmount + gapData)
            {
                newContract.ExtraData = dataPackage;
            }
            else
            {
                for (int i = allDataPackages.IndexOf(dataPackage)+1;i<allDataPackages.Count;i++)
                {
                    if (((decimal)historyAverage.data < allDataPackages[i].exDataAmount + gapData) || i == allDataPackages.Count-1)
                    {
                        newContract.ExtraData = allDataPackages[i];
                        break;
                    }
                }
            }

            return newContract;
        }

        /// <summary>
        /// Recommends package including handset, plan and extra package.
        /// </summary>
        /// <param name="mobileNumber">The mobile number of the customer</param>
        /// <returns>The contract contains recommended handset, plan and extra package</returns>
        public HandsetContract RecommendPackage(string mobileNumber)
        {
            HandsetContract contract = new HandsetContract();

            CustomerContract customerContract = customerManager.GetCustomerContractInformation(mobileNumber);
            if (customerContract != null)
            {
                List<CustomerUsageHistroy> historyRecords = customerManager.GetCustomerUseHistory(customerContract.contractID);
                if (historyRecords.Count > 0)
                {
                    contract = RecommendPlan(historyRecords, customerContract.HandsetContract);

                    List<V_HandsetBasicInformation> recommendedHandsets = RecommendHandsetsUsingAllRatings(customerContract.Customer.customerID, mobileNumber);

                    List<V_CapDetail> caps = planManager.ReadPlanCaps(contract.Cap.Plans.planID);

                    bool isHandsetFound = false;
                    foreach (V_HandsetBasicInformation handset in recommendedHandsets)
                    {
                        foreach (V_CapDetail cap in caps)
                        {
                            if (cap.handsetID == handset.handsetID)
                            {
                                HandsetBasicInformation recommendedHandset = handsetManager.GetHandsetBasicInformation(handset.handsetID);
                                contract.Cap.HandsetBasicInformation = recommendedHandset;
                                isHandsetFound = true;
                                break;
                            }
                        }
                        if (isHandsetFound)
                        {
                            break;
                        }
                    }
                }
            }

            return contract;
        }

        public void RateHandset(string customerID, string handsetID, int mark)
        {
            Rating rating = this.ObjectContext.RatingSet.Include("Customer").Include("HandsetBasicInformation").FirstOrDefault(
                r => r.Customer.customerID == customerID && r.HandsetBasicInformation.handsetID == handsetID);
            if (rating != null)
            {
                rating.ratingMark = (byte)mark;
            }
            else
            {
                rating = new Rating();
                string ID = DateTime.Now.ToString();
                rating.ratingID = ID;
                Customer customer = this.ObjectContext.CustomerSet.FirstOrDefault(c => c.customerID == customerID);
                rating.Customer = customer;
                HandsetBasicInformation handset = this.ObjectContext.HandsetBasicInformationSet.FirstOrDefault(h => h.handsetID == handsetID);
                rating.HandsetBasicInformation = handset;

                rating.ratingMark = (byte)mark;
                this.ObjectContext.AddToRatingSet(rating);
            }
            this.ObjectContext.SaveChanges();
        }

        public Rating GetRatingMark(string customerID, string handsetID)
        {
            Rating rating = this.ObjectContext.RatingSet.Include("Customer").Include("HandsetBasicInformation").FirstOrDefault(
                r => r.Customer.customerID == customerID && r.HandsetBasicInformation.handsetID == handsetID);
            return rating;
        }
    }
}
